package com.jinyou.utils.common;

import com.google.gson.Gson;
import com.jinyou.utils.common.dao.GeoAddressDetail;
import com.jinyou.utils.common.dao.GeoLatLngBackBean;
import com.jinyou.utils.common.dao.GeoServiceBackBean;
import o2o.utils.CommonUtils;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.util.*;

public class Geohash {
    private static Geohash geohash = new Geohash();
    private static int numbits = 6 * 5;
    final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
            'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};

    private Geohash() {
    }

    public static Geohash getInstance() {
        if (geohash == null) {
            geohash = new Geohash();
        }
        return geohash;
    }

    final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();

    static {
        int i = 0;
        for (char c : digits) {
            lookup.put(c, i++);
        }
    }

    /**
     * 将Geohash字串解码成经纬值
     *
     * @param geohash 待解码的Geohash字串
     * @return 经纬值数组
     */
    public static double[] decode(String geohash) {
        StringBuilder buffer = new StringBuilder();
        for (char c : geohash.toCharArray()) {
            int i = lookup.get(c) + 32;
            buffer.append(Integer.toString(i, 2).substring(1));
        }

        BitSet lonset = new BitSet();
        BitSet latset = new BitSet();

        // even bits
        int j = 0;
        for (int i = 0; i < numbits * 2; i += 2) {
            boolean isSet = false;
            if (i < buffer.length())
                isSet = buffer.charAt(i) == '1';
            lonset.set(j++, isSet);
        }

        // odd bits
        j = 0;
        for (int i = 1; i < numbits * 2; i += 2) {
            boolean isSet = false;
            if (i < buffer.length())
                isSet = buffer.charAt(i) == '1';
            latset.set(j++, isSet);
        }

        double lat = decode(latset, -90, 90);
        double lon = decode(lonset, -180, 180);

        DecimalFormat df = new DecimalFormat("0.000000");
        return new double[]{Double.parseDouble(df.format(lat)),
                Double.parseDouble(df.format(lon))};
    }

    /**
     * 根据二进制编码串和指定的数值变化范围，计算得到经/纬值
     *
     * @param bs      经/纬二进制编码串
     * @param floor   下限
     * @param ceiling 上限
     * @return 经/纬值
     */
    private static double decode(BitSet bs, double floor, double ceiling) {
        double mid = 0;
        for (int i = 0; i < bs.length(); i++) {
            mid = (floor + ceiling) / 2;
            if (bs.get(i))
                floor = mid;
            else
                ceiling = mid;
        }
        return mid;
    }

    /**
     * 根据经纬值得到Geohash字串
     *
     * @param lat 纬度值
     * @param lng 经度值
     * @return Geohash字串
     */
    public static String encode(Double lat, Double lng) {
        if (lat == null || lng == null) {
            return "";
        }
        BitSet latbits = getBits(lat, -90, 90);
        BitSet lonbits = getBits(lng, -180, 180);
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < numbits; i++) {
            buffer.append((lonbits.get(i)) ? '1' : '0');
            buffer.append((latbits.get(i)) ? '1' : '0');
        }
        return base32(Long.parseLong(buffer.toString(), 2));
    }

    /**
     * 将二进制编码串转换成Geohash字串
     *
     * @param i 二进制编码串
     * @return Geohash字串
     */
    private static String base32(long i) {
        char[] buf = new char[65];
        int charPos = 64;
        boolean negative = (i < 0);
        if (!negative)
            i = -i;
        while (i <= -32) {
            buf[charPos--] = digits[(int) (-(i % 32))];
            i /= 32;
        }
        buf[charPos] = digits[(int) (-i)];

        if (negative)
            buf[--charPos] = '-';
        return new String(buf, charPos, (65 - charPos));
    }

    /**
     * 得到经/纬度对应的二进制编码
     *
     * @param lat     经/纬度
     * @param floor   -90 下限
     * @param ceiling 90 上限
     * @return 二进制编码串
     */
    private static BitSet getBits(double lat, double floor, double ceiling) {
        BitSet buffer = new BitSet(numbits);
        for (int i = 0; i < numbits; i++) {
            double mid = (floor + ceiling) / 2;
            if (lat >= mid) {
                buffer.set(i);
                floor = mid;
            } else {
                ceiling = mid;
            }
        }
        return buffer;
    }

    /****************** 按照纬度0.004,经度0.007分成114750000个区域 445m*637m **********************/

    /**
     * 根据得到的经纬度获得一个区域号
     *
     * @param lat 3-53 0.004为一个分割，竖向12750
     * @param lng 73-136 0.007为一个分割，横向9000个
     * @return
     */
    public static long getArea(Double lat, Double lng) {
        if (lat == null || lng == null || lat == 0 || lng == 0) {
            return 0;
        }
        long latC = (long) ((Math.ceil((lat - 3.0) / 0.004) - 1) * 9000);
        long lngC = (long) (Math.ceil((lng - 73.0) / 0.007));
        return Long.parseLong(ToolUtils.getSize0(latC + lngC, 7));
    }

    /**
     * 根据层数获取 一层代表300m范围内 	 2层600m 3层1500m 4层2000m 5层3000m 6层3500，7层4000m 8层4500 9层5000m 左右 Jahnke 2016年6月3日 23:32:06
     *
     * @param point
     * @param number
     * @return
     */
    public static Set<Long> getAroundArea(long point, int number) {
        Set<Long> set = new HashSet<Long>();
        set.add(point); // 添加自己
        if (number < 1) {
            return set;
        }
        for (int i = 1; i <= number; i++) {
            for (int j = 1; j <= number; j++) {
                set.add(point + i * 9000); // 上面
                set.add(point + i * 9000 - j); // 上面左边
                set.add(point + i * 9000 + j); // 上面右边
                set.add(point - j); // 当前左边
                set.add(point + j); // 当前右边
                set.add(point - i * 9000); // 下面
                set.add(point - i * 9000 - j); // 下面左边
                set.add(point - i * 9000 + j); // 下面右边
            }
        }
        return set;
    }

    public static boolean isChinaLocal(Double lat, Double lng) {
        if (lat == null || lng == null) {
            return false;
        }
        if (lat < 3 || lat > 54) {
            return false;
        }
        if (lng < 73 || lng > 136) {
            return false;
        }
        return true;
    }

    /**
     * @param addr 查询的地址
     * @return
     * @throws IOException
     */
    public static Object[] getCoordinate(String addr) throws IOException {
        String lng = null;// 经度
        String lat = null;// 纬度
        String address = null;
        try {
            address = java.net.URLEncoder.encode(addr, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        String key = "F6h8oZqWciROeUE70pc7G3U7GczeerME";
        String url = String
                .format("http://api.map.baidu.com/geocoder?address=%s&output=json&key=%s",
                        address, key);
        URL myURL = null;
        URLConnection httpsConn = null;
        try {
            myURL = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        InputStreamReader insr = null;
        BufferedReader br = null;
        try {
            httpsConn = (URLConnection) myURL.openConnection();// 不使用代理
            if (httpsConn != null) {
                insr = new InputStreamReader(httpsConn.getInputStream(),
                        "UTF-8");
                br = new BufferedReader(insr);
                String data = null;
                int count = 1;
                while ((data = br.readLine()) != null) {
                    if (count == 5) {
                        lng = (String) data.subSequence(data.indexOf(":") + 1,
                                data.indexOf(","));// 经度
                        count++;
                    } else if (count == 6) {
                        lat = data.substring(data.indexOf(":") + 1);// 纬度
                        count++;
                    } else {
                        count++;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (insr != null) {
                insr.close();
            }
            if (br != null) {
                br.close();
            }
        }
        return new Object[]{lng, lat};
    }

    public static String getCity(Double lat, Double lng) throws IOException {
        String location = lng + "," + lat;
        try {
            location = java.net.URLEncoder.encode(location, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        String key = "ca5c946ddef3041742d674bd4abac64c";
        // String key = "0bc9c0196ba1c3adc346c174d2963083";
        String url = String
                .format("http://restapi.amap.com/v3/geocode/regeo?parameters&location=%s&output=json&key=%s",
                        location, key);
        URL myURL = null;
        URLConnection httpsConn = null;
        try {
            myURL = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        InputStreamReader insr = null;
        BufferedReader br = null;
        String city = "";
        try {
            httpsConn = (URLConnection) myURL.openConnection();// 不使用代理
            if (httpsConn != null) {
                insr = new InputStreamReader(httpsConn.getInputStream(),
                        "UTF-8");
                br = new BufferedReader(insr);
                String data = null;
                while ((data = br.readLine()) != null) {
                    System.out.println(data);
                    city = data.substring(data.indexOf("city") + 7,
                            data.indexOf("citycode") - 3);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (insr != null) {
                insr.close();
            }
            if (br != null) {
                br.close();
            }
        }
        return city;
    }

    public static GeoAddressDetail getAddressDetail(Double lat, Double lng) throws IOException {
        String location = lng + "," + lat;
        try {
            location = java.net.URLEncoder.encode(location, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        // String key = "0bc9c0196ba1c3adc346c174d2963083";
        String key = "ca5c946ddef3041742d674bd4abac64c";
        String url = String
                .format("http://restapi.amap.com/v3/geocode/regeo?parameters&location=%s&output=json&key=%s",
                        location, key);
        URL myURL = null;
        URLConnection httpsConn = null;
        try {
            myURL = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        InputStreamReader insr = null;
        BufferedReader br = null;

        GeoAddressDetail addressDetail = new GeoAddressDetail();

        try {
            httpsConn = (URLConnection) myURL.openConnection();// 不使用代理
            if (httpsConn != null) {
                insr = new InputStreamReader(httpsConn.getInputStream(),
                        "UTF-8");
                br = new BufferedReader(insr);
                String data = null;

                while ((data = br.readLine()) != null) {
//                    System.out.println(data);


//                    String province = data.substring(data.indexOf("province") + 11, data.indexOf("city") - 3);
//                    String city = data.substring(data.indexOf("city") + 7, data.indexOf("citycode") - 3);
//                    String county = data.substring(data.indexOf("district") + 11, data.indexOf("adcode") - 3);
//                    String address = data.substring(data.indexOf("formatted_address") + 20, data.indexOf("addressComponent") - 3);
                    String province = null;
                    String city = null;
                    String county = null;
                    String address = null;


                    GeoServiceBackBean bean = new Gson().fromJson(data, GeoServiceBackBean.class);
                    if (null != bean && null != bean.getRegeocode() && null != bean.getRegeocode().getAddressComponent()) {
                        province = bean.getRegeocode().getAddressComponent().getProvince();
                        city = bean.getRegeocode().getAddressComponent().getCity();
                        county = bean.getRegeocode().getAddressComponent().getCountry();
                        address = bean.getRegeocode().getFormatted_address();
                    }

                    addressDetail.setProvince(province);
                    addressDetail.setCity(city);
                    addressDetail.setCounty(county);
                    addressDetail.setAddress(address);

                    addressDetail.setShortProvince(subLastOneChar(province));
                    addressDetail.setShortCity(subLastOneChar(city));
                    if (ValidateUtil.isNull(city) && ValidateUtil.isNotNull(province)) {
                        addressDetail.setCity(province);
                        addressDetail.setShortCity(subLastOneChar(province));
                    }
                    addressDetail.setShortAddress(shortAddress(address, province, city, county));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (insr != null) {
                insr.close();
            }
            if (br != null) {
                br.close();
            }
        }

        return addressDetail;
    }


    public static GeoAddressDetail getLatLng(String localAddress) throws IOException {
        try {
            localAddress = java.net.URLEncoder.encode(localAddress, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        // String key = "0bc9c0196ba1c3adc346c174d2963083";
        String key = "ca5c946ddef3041742d674bd4abac64c";
        String url = String
                .format("https://restapi.amap.com/v3/geocode/geo?parameters&address=%s&output=json&key=%s",
                        localAddress, key);
        URL myURL = null;
        URLConnection httpsConn = null;
        try {
            myURL = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        InputStreamReader insr = null;
        BufferedReader br = null;

        GeoAddressDetail addressDetail = new GeoAddressDetail();

        try {
            httpsConn = (URLConnection) myURL.openConnection();// 不使用代理
            if (httpsConn != null) {
                insr = new InputStreamReader(httpsConn.getInputStream(),
                        "UTF-8");
                br = new BufferedReader(insr);
                String data = null;

                while ((data = br.readLine()) != null) {
//                    System.out.println(data);


//                    String province = data.substring(data.indexOf("province") + 11, data.indexOf("city") - 3);
//                    String city = data.substring(data.indexOf("city") + 7, data.indexOf("citycode") - 3);
//                    String county = data.substring(data.indexOf("district") + 11, data.indexOf("adcode") - 3);
//                    String address = data.substring(data.indexOf("formatted_address") + 20, data.indexOf("addressComponent") - 3);
                    String province = null;
                    String city = null;
                    String county = null;
                    String location = null;

                    System.out.println(data);
                    GeoLatLngBackBean bean = new Gson().fromJson(data, GeoLatLngBackBean.class);
                    if (null != bean && null != bean.getGeocodes() && ValidateUtil.isAbsList(bean.getGeocodes())) {
                        province = bean.getGeocodes().get(0).getProvince();
                        city = bean.getGeocodes().get(0).getCity();
                        county = bean.getGeocodes().get(0).getDistrict();
                        location = bean.getGeocodes().get(0).getLocation();
                    }

                    addressDetail.setProvince(province);
                    addressDetail.setCity(city);
                    addressDetail.setCounty(county);
                    if (ValidateUtil.isNotNull(location)) {
                        String[] split = location.split(",");
                        if (split.length == 2) {
                            addressDetail.setLat(Double.parseDouble(split[1]));
                            addressDetail.setLng(Double.parseDouble(split[0]));
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (insr != null) {
                insr.close();
            }
            if (br != null) {
                br.close();
            }
        }
        return addressDetail;
    }

    private static String subLastOneChar(String str) {
        if (ValidateUtil.isNotNull(str) && str.length() > 1) {
            return str.substring(0, str.length() - 1);
        }
        return "";
    }

    private static String shortAddress(String address, String province, String city, String county) {
        if (ValidateUtil.isNull(address)) {
            return "";
        }
        if (ValidateUtil.isNull(province) && ValidateUtil.isNull(city) && ValidateUtil.isNull(county)) {
            return address;
        }
        int len = (province + city + county).length();
        if (address.length() > len) {
            return address.substring(len, address.length());
        }
        return address;
    }


    private static String getShortCity(Double lat, Double lng) throws IOException {
        String city = getCity(lat, lng);
        if (ValidateUtil.isNull(city)) {
            return "";
        }
        if (city.length() > 1 && 0 == city.lastIndexOf("市") - (city.length() - 1)) {
            return city.substring(0, city.length() - 1);
        }
        return city;
    }

    /**
     * 计算地球上任意两点(经纬度)距离
     *
     * @param lng1 第一点经度
     * @param lat1 第一点纬度
     * @param lng2 第二点经度
     * @param lat2 第二点纬度
     * @return 返回距离 单位：米
     */
    public static double distance(double lat1, double lng1,
                                  double lat2, double lng2) {
        double a, b, R;
        R = 6378137; // 地球半径
        lat1 = lat1 * Math.PI / 180.0;
        lat2 = lat2 * Math.PI / 180.0;
        a = lat1 - lat2;
        b = (lng1 - lng2) * Math.PI / 180.0;
        double d;
        double sa2, sb2;
        sa2 = Math.sin(a / 2.0);
        sb2 = Math.sin(b / 2.0);
        d = 2
                * R
                * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1)
                * Math.cos(lat2) * sb2 * sb2));
        return d;
    }


    /**
     * 判断是否在多边形区域内
     *
     * @param pointLon 要判断的点的纵坐标
     * @param pointLat 要判断的点的横坐标
     * @param lon      区域各顶点的纵坐标数组
     * @param lat      区域各顶点的横坐标数组
     * @return
     */
    public static boolean isInPolygon(double pointLon, double pointLat, Double[] lon, Double[] lat) {
        // 将要判断的横纵坐标组成一个点
        Point2D.Double point = new Point2D.Double(pointLon, pointLat);
        // 将区域各顶点的横纵坐标放到一个点集合里面
        List<Point2D.Double> pointList = new ArrayList<Point2D.Double>();
        double polygonPoint_x = 0.0, polygonPoint_y = 0.0;
        for (int i = 0; i < lon.length; i++) {
            polygonPoint_x = lon[i];
            polygonPoint_y = lat[i];
            Point2D.Double polygonPoint = new Point2D.Double(polygonPoint_x, polygonPoint_y);
            pointList.add(polygonPoint);
        }
        return check(point, pointList);
    }

    /**
     * 一个点是否在多边形内
     *
     * @param point   要判断的点的横纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    private static boolean check(Point2D.Double point, List<Point2D.Double> polygon) {
        java.awt.geom.GeneralPath peneralPath = new java.awt.geom.GeneralPath();

        Point2D.Double first = polygon.get(0);
        // 通过移动到指定坐标（以双精度指定），将一个点添加到路径中
        peneralPath.moveTo(first.x, first.y);
        polygon.remove(0);
        for (Point2D.Double d : polygon) {
            // 通过绘制一条从当前坐标到新指定坐标（以双精度指定）的直线，将一个点添加到路径中。
            peneralPath.lineTo(d.x, d.y);
        }
        // 将几何多边形封闭
        peneralPath.lineTo(first.x, first.y);
        peneralPath.closePath();
        // 测试指定的 Point2D 是否在 Shape 的边界内。
        return peneralPath.contains(point);
    }


    public static void main(String[] args) throws IOException {
        String address = "济南市历下区力高国际";
        GeoAddressDetail detail = getLatLng(address);
        System.out.println(detail.getLat());
        System.out.println(detail.getLng());
//        double lng = 117.058989;
//        double lat = 36.678755;

//        Double[] locals = null;
//        String[] lll = local.split(";");
//        locals = new Double[]{Double.parseDouble(lll[0]), Double.parseDouble(lll[1])};

//        double[] arr = new double[]{117.056345, 117.062525, 117.062439, 117.057718, 117.056045, 117.056431};
//        double[] arr1 = new double[]{36.67882, 36.678683, 36.672074, 36.672039, 36.672125, 36.67407};
//        Boolean flag = isInPolygon(lng, lat, arr, arr1);
//        System.out.println(flag);
//		Set<Long> set = getAroundArea(1156325, 10);
//		System.out.println(set.toString().replace("[", "(").replace("]", ")"));
//		System.out.println(set.size());
//
//		Object[] o = getCoordinate("成都市天府四街");
//		System.out.println(o[0]);// 经度
//		System.out.println(o[1]);// 纬度
//		String city = getName(30.548398, 104.064766);
//		System.out.println(city);

//		System.out.println(distance(35.231545, 95.561256, 35.235545, 95.561256));

//        Long areaPoint = Geohash.getArea(22.9799710, 113.7252250);
//        Set<Long> areaPoints = Geohash.getAroundArea(areaPoint, 5);
//
//        System.out.println(distance(22.9799710, 113.7252250, 23.0021711, 113.7372281));
//        if (areaPoints.contains(getArea(23.0021711, 113.7372281))) {
//            System.out.println("1you");
//        }
//        System.out.println(distance(22.9799710, 113.7252250, 22.9959507, 113.7589111));
//        if (areaPoints.contains(getArea(22.9959507, 113.7589111))) {
//            System.out.println("2you");
//        }
    }
}
